```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Kafka如何不消费重复数据 | 技术小馆</title>
    <link rel="stylesheet" href="https://cdn.staticfile.org/font-awesome/6.4.0/css/all.min.css">
    <link rel="stylesheet" href="https://cdn.staticfile.org/tailwindcss/2.2.19/tailwind.min.css">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/mermaid@latest/dist/mermaid.min.js"></script>
    <style>
        body {
            font-family: 'Noto Sans SC', Tahoma, Arial, Roboto, "Droid Sans", "Helvetica Neue", "Droid Sans Fallback", "Heiti SC", "Hiragino Sans GB", Simsun, sans-serif;
            line-height: 1.7;
            color: #333;
        }
        .hero-bg {
            background: linear-gradient(135deg, #6e8efb 0%, #a777e3 100%);
        }
        .code-block {
            background-color: #282c34;
            border-radius: 8px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
        }
        .section-card {
            transition: all 0.3s ease;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
        }
        .section-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
        }
        .mermaid {
            background-color: #f8fafc;
            padding: 20px;
            border-radius: 8px;
            margin: 20px 0;
        }
        .highlight {
            background-color: rgba(110, 142, 251, 0.1);
            padding: 2px 4px;
            border-radius: 4px;
            font-weight: 500;
        }
    </style>
</head>
<body class="bg-gray-50">
    <!-- Hero Section -->
    <div class="hero-bg text-white py-20 px-4 md:px-0">
        <div class="container mx-auto text-center">
            <h1 class="text-4xl md:text-5xl font-bold mb-6 font-serif">Kafka如何不消费重复数据</h1>
            <p class="text-xl md:text-2xl max-w-3xl mx-auto mb-8">分布式消息系统中的精准消费解决方案</p>
            <div class="w-24 h-1 bg-white bg-opacity-50 mx-auto mb-10"></div>
            <p class="text-lg max-w-2xl mx-auto opacity-90">确保Kafka消费者不会重复处理消息的全面指南，从偏移量管理到幂等性设计</p>
        </div>
    </div>

    <!-- Main Content -->
    <div class="container mx-auto px-4 py-12 max-w-6xl">
        <!-- Introduction -->
        <div class="bg-white rounded-xl p-8 mb-12 section-card">
            <h2 class="text-3xl font-bold mb-6 text-gray-800 font-serif">问题背景</h2>
            <p class="text-gray-700 mb-6">当涉及到使用Kafka作为消息中间件时，确保不消费重复数据是至关重要的。重复消费可能会导致数据的不一致性，影响系统的正确性和稳定性。为了解决这个问题，我们需要采取一系列措施来确保消费者不会重复处理已经消费过的消息。</p>
            <img src="https://cdn.nlark.com/yuque/0/2024/png/21449790/1712717709828-f0fef3ba-2ae9-4ca8-92e6-68932f9a7efb.png" alt="Kafka重复消费问题" class="w-full rounded-lg mb-6">
            <p class="text-gray-700">由于Kafka是一个分布式消息队列，它具有高吞吐量和可扩展性的特点。这意味着消息可能会被多个消费者并发地消费，而且网络延迟、故障恢复等因素也可能导致消息重复消费。生产者在发送消息时可以配置消息的幂等性属性，确保同一条消息在重试发送时不会被重复处理。Kafka提供了幂等性的生产者，它使用消息序列号来确保消息的唯一性。</p>
        </div>

        <!-- Section 1 -->
        <div class="grid md:grid-cols-2 gap-8 mb-12">
            <div class="bg-white rounded-xl p-8 section-card">
                <div class="flex items-center mb-4">
                    <div class="bg-blue-100 text-blue-600 p-3 rounded-full mr-4">
                        <i class="fas fa-project-diagram text-xl"></i>
                    </div>
                    <h2 class="text-2xl font-bold text-gray-800 font-serif">消息消费的偏移量管理</h2>
                </div>
                <p class="text-gray-700 mb-6">Kafka使用偏移量（offset）来标识消费者在分区中已经消费的消息位置。消费者会定期提交偏移量，以记录自己已经消费的消息位置。通过管理偏移量，可以避免重复消费数据。</p>
                <img src="https://cdn.nlark.com/yuque/0/2024/png/21449790/1712718050724-3bb94645-6147-4dc7-9e68-db3b4b882936.png" alt="偏移量管理" class="w-full rounded-lg mb-6">
                <div class="space-y-4">
                    <div>
                        <h3 class="font-bold text-gray-800 mb-2">消费者提交偏移量</h3>
                        <p class="text-gray-700">消费者需要定期提交偏移量，以记录自己已经消费的消息位置。这可以通过定期将最新的偏移量提交到Kafka服务器来实现。一般来说，消费者在处理完一批消息后，会异步或同步地提交偏移量。</p>
                    </div>
                    <div>
                        <h3 class="font-bold text-gray-800 mb-2">自动提交偏移量</h3>
                        <p class="text-gray-700">Kafka提供了自动提交偏移量的功能，即在消费者处理消息后自动将偏移量提交到Kafka服务器。但是需要注意的是，自动提交偏移量可能会导致一些问题，比如消息处理失败后偏移量已经提交但消息没有成功处理。</p>
                    </div>
                </div>
            </div>

            <div class="bg-white rounded-xl p-8 section-card">
                <div class="flex items-center mb-4">
                    <div class="bg-purple-100 text-purple-600 p-3 rounded-full mr-4">
                        <i class="fas fa-key text-xl"></i>
                    </div>
                    <h2 class="text-2xl font-bold text-gray-800 font-serif">消息幂等性</h2>
                </div>
                <p class="text-gray-700 mb-6">生产者在发送消息时可以设置消息的幂等性，确保同一条消息在重试发送时不会被重复处理。Kafka提供了幂等性的生产者，它通过序列号和分区来确保相同的消息在同一个分区中只会被处理一次。</p>
                <img src="https://cdn.nlark.com/yuque/0/2024/png/21449790/1712718127917-73ff8265-772f-4e7a-93ec-5cfef8776ebc.png" alt="消息幂等性" class="w-full rounded-lg mb-6">
                <div class="space-y-4">
                    <div>
                        <h3 class="font-bold text-gray-800 mb-2">生产者消息序列号</h3>
                        <p class="text-gray-700">生产者在发送消息时可以为每条消息赋予一个唯一的序列号。Kafka提供了幂等性的生产者，它通过序列号来确保相同的消息在同一个分区中只会被处理一次。这样即使消息被重复发送，消费者也只会处理一次。</p>
                    </div>
                    <div>
                        <h3 class="font-bold text-gray-800 mb-2">生产者幂等性配置</h3>
                        <p class="text-gray-700">Kafka提供了生产者幂等性的配置选项，生产者可以通过设置<span class="highlight">enable.idempotence</span>参数来开启幂等性。开启幂等性后，生产者会自动处理重复发送的消息，确保消息只会被处理一次。</p>
                    </div>
                </div>
            </div>
        </div>

        <!-- Section 2 -->
        <div class="bg-white rounded-xl p-8 mb-12 section-card">
            <div class="flex items-center mb-4">
                <div class="bg-green-100 text-green-600 p-3 rounded-full mr-4">
                    <i class="fas fa-exchange-alt text-xl"></i>
                </div>
                <h2 class="text-2xl font-bold text-gray-800 font-serif">使用事务</h2>
            </div>
            <p class="text-gray-700 mb-6">在Kafka中，可以使用事务来确保消息的原子性和一致性。消费者可以在处理消息时使用事务，确保消息的处理过程中不会出现意外的中断或重复处理。</p>
            <img src="https://cdn.nlark.com/yuque/0/2024/png/21449790/1712718218101-87ff7dff-d1c9-4e5c-a636-804a9841ab3f.png" alt="Kafka事务" class="w-full rounded-lg mb-6">
            <div class="grid md:grid-cols-2 gap-6 mb-6">
                <div>
                    <h3 class="font-bold text-gray-800 mb-2">事务性生产者</h3>
                    <p class="text-gray-700">首先，要确保生产者在发送消息时使用事务性生产者。事务性生产者能够确保一批消息要么全部被发送，要么全部不发送，从而避免了消息的部分提交。</p>
                </div>
                <div>
                    <h3 class="font-bold text-gray-800 mb-2">消费者位移管理</h3>
                    <p class="text-gray-700">消费者需要在处理完消息后，手动提交消费位移。这样可以确保消费者组可以正确地跟踪每个消费者在每个分区中消费的位置，从而避免了重复消费。</p>
                </div>
            </div>
            
            <div class="mt-6">
                <h3 class="font-bold text-gray-800 mb-4">代码示例</h3>
                <div class="code-block p-6 rounded-lg mb-4">
                    <pre class="text-gray-200 overflow-x-auto">
<code>import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class KafkaProducer {

    @Autowired
    private KafkaTemplate&lt;String, String&gt; kafkaTemplate;

    @Transactional
    public void sendMessage(String message) {
        kafkaTemplate.send("my-topic", message);
    }
}</code>
                    </pre>
                </div>
                <div class="code-block p-6 rounded-lg">
                    <pre class="text-gray-200 overflow-x-auto">
<code>import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

@Component
public class KafkaConsumer {

    @KafkaListener(topics = "my-topic", groupId = "my-consumer-group")
    public void listen(ConsumerRecord&lt;String, String&gt; record) {
        System.out.println("Received message: " + record.value());
    }
}</code>
                    </pre>
                </div>
            </div>
        </div>

        <!-- Section 3 -->
        <div class="grid md:grid-cols-2 gap-8 mb-12">
            <div class="bg-white rounded-xl p-8 section-card">
                <div class="flex items-center mb-4">
                    <div class="bg-yellow-100 text-yellow-600 p-3 rounded-full mr-4">
                        <i class="fas fa-filter text-xl"></i>
                    </div>
                    <h2 class="text-2xl font-bold text-gray-800 font-serif">消息去重</h2>
                </div>
                <p class="text-gray-700 mb-6">在消费者端可以实现消息去重的逻辑，避免处理重复消息。消费者可以维护一个已经处理过的消息的记录，通过比对消息的唯一标识（如消息ID）来判断是否重复。</p>
                <img src="https://cdn.nlark.com/yuque/0/2024/png/21449790/1712718368803-4c046d92-dc09-4e60-a023-899a6bbd74b5.png" alt="消息去重" class="w-full rounded-lg mb-6">
                <div class="space-y-4">
                    <div>
                        <h3 class="font-bold text-gray-800 mb-2">消费者端消息记录</h3>
                        <p class="text-gray-700">消费者可以维护一个已经处理过的消息记录，记录已经消费过的消息的唯一标识（如消息ID）。这个记录可以是内存中的数据结构，也可以是持久化存储（如数据库、Redis等）中的数据。</p>
                    </div>
                    <div>
                        <h3 class="font-bold text-gray-800 mb-2">消息去重逻辑</h3>
                        <p class="text-gray-700">在消费者处理消息之前，会先检查消息的唯一标识是否已经在已处理消息记录中存在。如果消息的唯一标识已经存在，则认为这条消息是重复消息，不进行处理。</p>
                    </div>
                </div>
                <div class="mt-6">
                    <div class="code-block p-6 rounded-lg">
                        <pre class="text-gray-200 overflow-x-auto">
<code>import java.util.HashSet;
import java.util.Set;

public class MessageDeduplication {
    private Set&lt;String&gt; processedMessages = new HashSet&lt;&gt;();

    public void processMessage(String messageId, String messageContent) {
        if (!processedMessages.contains(messageId)) {
            System.out.println("Processing message: " + messageContent);
            processedMessages.add(messageId);
        } else {
            System.out.println("Skipping duplicate message: " + messageContent);
        }
    }
}</code>
                        </pre>
                    </div>
                </div>
            </div>

            <div class="bg-white rounded-xl p-8 section-card">
                <div class="flex items-center mb-4">
                    <div class="bg-red-100 text-red-600 p-3 rounded-full mr-4">
                        <i class="fas fa-users text-xl"></i>
                    </div>
                    <h2 class="text-2xl font-bold text-gray-800 font-serif">使用消费者组</h2>
                </div>
                <p class="text-gray-700 mb-6">Kafka支持将消费者组中的多个消费者协同工作来消费同一个主题的消息。通过使用消费者组，可以确保同一条消息只会被消费者组中的一个消费者处理，从而避免重复消费。</p>
                <img src="https://cdn.nlark.com/yuque/0/2024/png/21449790/1712731914062-5ac2aa34-efa1-43b8-a848-8f187a0cae31.png" alt="消费者组" class="w-full rounded-lg mb-6">
                <div class="space-y-4">
                    <div>
                        <h3 class="font-bold text-gray-800 mb-2">消费者组协调器</h3>
                        <p class="text-gray-700">Kafka的消费者组协调器负责管理消费者组中各个消费者的分配和协调工作。它会负责分配分区给消费者，并处理偏移量的提交和查询请求。</p>
                    </div>
                    <div>
                        <h3 class="font-bold text-gray-800 mb-2">分区分配策略</h3>
                        <p class="text-gray-700">消费者组协调器会根据分区分配策略将分区分配给消费者。Kafka提供了几种不同的分区分配策略，如轮询（Round-robin）、范围（Range）等。</p>
                    </div>
                </div>
                <div class="mt-6">
                    <div class="code-block p-6 rounded-lg">
                        <pre class="text-gray-200 overflow-x-auto">
<code>import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

@Component
public class KafkaConsumer {

    @KafkaListener(topics = "my-topic", groupId = "my-consumer-group")
    public void listen(String message) {
        System.out.println("Received message: " + message);
    }
}</code>
                        </pre>
                    </div>
                </div>
            </div>
        </div>

        <!-- Section 4 -->
        <div class="grid md:grid-cols-2 gap-8 mb-12">
            <div class="bg-white rounded-xl p-8 section-card">
                <div class="flex items-center mb-4">
                    <div class="bg-indigo-100 text-indigo-600 p-3 rounded-full mr-4">
                        <i class="fas fa-clock text-xl"></i>
                    </div>
                    <h2 class="text-2xl font-bold text-gray-800 font-serif">时间窗口去重</h2>
                </div>
                <p class="text-gray-700 mb-6">可以采用时间窗口的方式来去重。例如，在消费者端维护一个时间窗口，在一定的时间范围内，只接收一次相同消息。如果在时间窗口内收到相同的消息，则视为重复消息，不进行处理。</p>
                <img src="https://cdn.nlark.com/yuque/0/2024/png/21449790/1712732091278-eed72043-cc59-48fd-8a79-545e0b18ade0.png" alt="时间窗口去重" class="w-full rounded-lg mb-6">
                <div class="space-y-4">
                    <div>
                        <h3 class="font-bold text-gray-800 mb-2">维护时间窗口</h3>
                        <p class="text-gray-700">在消费者端维护一个时间窗口，记录每条消息的时间戳以及处理时间窗口的起始时间和结束时间。</p>
                    </div>
                    <div>
                        <h3 class="font-bold text-gray-800 mb-2">消息去重逻辑</h3>
                        <p class="text-gray-700">在处理每条消息时，先检查消息的时间戳是否在当前时间窗口内。如果消息的时间戳在当前时间窗口内，则处理消息；否则，丢弃该消息。</p>
                    </div>
                </div>
                <div class="mt-6">
                    <div class="code-block p-6 rounded-lg">
                        <pre class="text-gray-200 overflow-x-auto">
<code>import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;

@Component
public class KafkaConsumer {
    private static final long TIME_WINDOW_DURATION_MS = 60000;
    private Map&lt;String, Instant&gt; processedMessages = new HashMap&lt;&gt;();

    @KafkaListener(topics = "my-topic", groupId = "my-consumer-group")
    public void listen(ConsumerRecord&lt;String, String&gt; record) {
        Instant now = Instant.now();
        String messageId = record.key();
        Instant messageTimestamp = Instant.ofEpochMilli(record.timestamp());

        if (processedMessages.containsKey(messageId)) {
            if (processedMessages.get(messageId).isAfter(now.minusMillis(TIME_WINDOW_DURATION_MS))) {
                System.out.println("Skipping duplicate message: " + record.value());
                return;
            }
        }

        System.out.println("Processing message: " + record.value());
        processedMessages.put(messageId, messageTimestamp);
        processedMessages.entrySet().removeIf(entry -> 
            entry.getValue().isBefore(now.minusMillis(TIME_WINDOW_DURATION_MS)));
    }
}</code>
                        </pre>
                    </div>
                </div>
            </div>

            <div class="bg-white rounded-xl p-8 section-card">
                <div class="flex items-center mb-4">
                    <div class="bg-pink-100 text-pink-600 p-3 rounded-full mr-4">
                        <i class="fas fa-list-ol text-xl"></i>
                    </div>
                    <h2 class="text-2xl font-bold text-gray-800 font-serif">消息序列号</h2>
                </div>
                <p class="text-gray-700 mb-6">在消息的生产端给每条消息赋予一个唯一的序列号，消费端通过序列号来判断消息的唯一性。消费者维护一个已处理过的序列号列表，通过比对消息的序列号来判断是否重复。</p>
                <img src="https://cdn.nlark.com/yuque/0/2024/png/21449790/1712732269716-2a8b56f3-d2ff-4694-9d38-d160e1cbbd9b.png" alt="消息序列号" class="w-full rounded-lg mb-6">
                <div class="space-y-4">
                    <div>
                        <h3 class="font-bold text-gray-800 mb-2">为消息添加序列号</h3>
                        <p class="text-gray-700">在生产者端，为每条消息生成一个唯一的序列号或者消息ID，并将其包含在消息的元数据中。这样可以确保每条消息都有一个唯一标识符。</p>
                    </div>
                    <div>
                        <h3 class="font-bold text-gray-800 mb-2">维护已处理序列号集合</h3>
                        <p class="text-gray-700">在消费者端维护一个已经处理过的消息序列号的集合。当消费者接收到一条消息时，先检查消息的序列号是否在已处理过的消息序列号集合中。</p>
                    </div>
                </div>
                <div class="mt-6">
                    <div class="code-block p-6 rounded-lg">
                        <pre class="text-gray-200 overflow-x-auto">
<code>import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;

@Component
public class KafkaConsumer {
    private Set&lt;String&gt; processedMessageIds = new HashSet&lt;&gt;();

    @KafkaListener(topics = "my-topic", groupId = "my-consumer-group")
    public void listen(ConsumerRecord&lt;String, String&gt; record) {
        String messageId = record.key();

        if (processedMessageIds.contains(messageId)) {
            System.out.println("Skipping duplicate message: " + record.value());
            return;
        }

        System.out.println("Processing message: " + record.value());
        processedMessageIds.add(messageId);
    }
}</code>
                        </pre>
                    </div>
                </div>
            </div>
        </div>

        <!-- Section 5 -->
        <div class="bg-white rounded-xl p-8 mb-12 section-card">
            <div class="flex items-center mb-4">
                <div class="bg-teal-100 text-teal-600 p-3 rounded-full mr-4">
                    <i class="fas fa-code-branch text-xl"></i>
                </div>
                <h2 class="text-2xl font-bold text-gray-800 font-serif">数据分区和分片</h2>
            </div>
            <p class="text-gray-700 mb-6">在Kafka中，数据被分成多个分区，每个分区可以有多个副本。通过合理的分区设计和数据分片策略，可以避免数据的重复消费。消费者可以根据分区信息来判断是否处理过某条消息，从而避免重复处理。</p>
            <img src="https://cdn.nlark.com/yuque/0/2024/png/21449790/1712732347172-b9d7050c-d970-4958-9af1-97a3213adebf.png" alt="数据分区" class="w-full rounded-lg mb-6">
            <div class="grid md:grid-cols-2 gap-6 mb-6">
                <div>
                    <h3 class="font-bold text-gray-800 mb-2">使用消息key</h3>
                    <p class="text-gray-700">在消息中设置唯一的key，确保具有相同key的消息被分配到同一个分区中。这样，即使同一个消息被发送多次，也只会被写入到同一个分区中，从而保证了消息的顺序性。</p>
                </div>
                <div>
                    <h3 class="font-bold text-gray-800 mb-2">使用消费者组</h3>
                    <p class="text-gray-700">当多个消费者组同时消费同一个主题的消息时，每个消费者组会独立地维护消费偏移量。因此，即使同一个消息被不同的消费者组消费，也不会出现重复消费的情况。</p>
                </div>
            </div>
            <div class="mt-6">
                <div class="code-block p-6 rounded-lg">
                    <pre class="text-gray-200 overflow-x-auto">
<code>import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

@Component
public class KafkaConsumer {

    @KafkaListener(topics = "my-topic", groupId = "group1")
    public void listenGroup1(ConsumerRecord&lt;String, String&gt; record) {
        System.out.println("Group1 Received message: " + record.value());
    }

    @KafkaListener(topics = "my-topic", groupId = "group2")
    public void listenGroup2(ConsumerRecord&lt;String, String&gt; record) {
        System.out.println("Group2 Received message: " + record.value());
    }
}</code>
                    </pre>
                </div>
            </div>
        </div>

        <!-- Summary Diagram -->
        <div class="bg-white rounded-xl p-8 mb-12 section-card">
            <h2 class="text-3xl font-bold mb-6 text-gray-800 font-serif">Kafka去重策略总结</h2>
            <div class="mermaid">
                graph TD
                    A[Kafka去重策略] --> B[生产者端]
                    A --> C[消费者端]
                    B --> B1[幂等性生产者]
                    B --> B2[事务性生产者]
                    C --> C1[偏移量管理]
                    C --> C2[消息去重]
                    C --> C3[消费者组]
                    C2 --> C21[序列号比对]
                    C2 --> C22[时间窗口]
                    C2 --> C23[分区策略]
            </div>
            <div class="mt-6 grid md:grid-cols-3 gap-4">
                <div class="bg-blue-50 p-4 rounded-lg">
                    <h3 class="font-bold text-blue-800 mb-2">生产者策略</h3>
                    <ul class="space-y-1 text-blue-700">
                        <li>• 幂等性生产者</li>
                        <li>• 事务性生产者</li>
                        <li>• 消息序列号</li>
                    </ul>
                </div>
                <div class="bg-green-50 p-4 rounded-lg">
                    <h3 class="font-bold text-green-800 mb-2">消费者策略</h3>
                    <ul class="space-y-1 text-green-700">
                        <li>• 偏移量管理</li>
                        <li>• 消费者组</li>
                        <li>• 分区分配</li>
                    </ul>
                </div>
                <div class="bg-purple-50 p-4 rounded-lg">
                    <h3 class="font-bold text-purple-800 mb-2">去重技术</h3>
                    <ul class="space-y-1 text-purple-700">
                        <li>• 序列号比对</li>
                        <li>• 时间窗口</li>
                        <li>• 存储记录</li>
                    </ul>
                </div>
            </div>
        </div>
    </div>

    <!-- Footer -->
    <footer class="bg-gray-900 text-white py-8">
        <div class="container mx-auto px-4 text-center">
            <div class="mb-4">
                <h3 class="text-xl font-bold mb-2">技术小馆</h3>
                <p class="text-gray-400">探索技术的无限可能</p>
            </div>
            <div class="flex justify-center space-x-4">
                <a href="http://www.yuque.com/jtostring" class="text-gray-300 hover:text-white transition duration-300">
                    <i class="fas fa-book-open mr-2"></i>语雀文档
                </a>
            </div>
            <div class="mt-6 pt-6 border-t border-gray-800 text-gray-500 text-sm">
                &copy; 2023 技术小馆. 保留所有权利.
            </div>
        </div>
    </footer>

    <script>
        mermaid.initialize({
            startOnLoad: true,
            theme: 'default',
            flowchart: {
                useMaxWidth: true,
                htmlLabels: true,
                curve: 'basis'
            }
        });
    </script>
</body>
</html>
```